import { Node, UIOpacity, instantiate, isValid, tween, v3 } from "cc";
import Toast from "../comps/Toast";
import BasePanel from "./BasePanel";
import BasePopup from "./BasePopup";
import { WanderCommon } from "./Defines";
import Foundation from "./Foundation";
import Log from "./LogMgr";
import Pool from "./PoolMgr";
import Res from "./ResMgr";
import Singleton from "./Singleton";

interface IPanelData {
    uuid: string;
    comp: BasePanel | null;
}

interface IPopupData {
    uuid: string;
    comp: BasePopup | null;
}

class GuiMgr extends Singleton {

    public toast(msg: string, colorHex = '#FFFFFF') {
        let index = this._flyMsgArr.findIndex(v => {
            return v.msg == msg;
        });
        if (index !== -1) return;
        this._flyMsgArr.push({ msg, colorHex });
        if (this._isFlying) return;
        this._checkFlyMsg();
    }

    public setRequestMask(visible: boolean) {
    }

    /**
     * 全屏界面
     * 多个界面可叠加，只显示最上层
     * @param bdlname 包名
     * @param clsname
     * @param data
     */
    public createPanel(bdlname: string, clsname: string, data?: any, completedCb?: (hasErr: boolean) => void) {
        const uuid = clsname;
        for (let panelData of this._panelDatas) {
            if (panelData.uuid == uuid) return;
        }
        this._panelDatas.push({ uuid, comp: null });

        this.setLoadingMask(true);
        Res.manualLoadPrefab(bdlname, clsname)
            .then(prefab => {
                let parent = Res.getRoot(WanderCommon.LayerMap.Panel);
                this._panelDatas.forEach(panel => {
                    if (panel.uuid != uuid && panel.comp && isValid(panel.comp)) {
                        panel.comp.setVisible(false);
                    }
                });
                let node = instantiate(prefab),
                    comp = node.getComponent(clsname) as BasePanel;
                comp.setData(data);
                parent.addChild(node);

                this.insertComp(uuid, comp);
                completedCb && completedCb(true);
                this.setLoadingMask(false);
            })
            .catch((err: Error) => {
                Log.e(`create ${clsname} err: ` + err.message);
                this.removePanelData(uuid, 0);
                completedCb && completedCb(false);
                this.setLoadingMask(false);
            });
    }

    /**
     * 显示弹窗
     * @param bdlname 包名
     * @param clsname 类名 注意：预制体命名必须和挂载脚本类名相同
     * @param suffix 后缀，不同后缀的同名 Popup 可以同时呼出
     * @param hideMask 是否隐藏遮罩
     * @param depth 以最上层 Popup 为基准，显示 depth 深度下的所有 Popup
     */
    public createPopup(bdlname: string, clsname: string, data?: any, suffix?: string, hideMask: boolean = false, depth: number = 0) {
        const uuid = suffix ? `${clsname}_${suffix}` : clsname;
        for (let popup of this._popupDatas) {
            if (popup.uuid == uuid) return;
        }
        this._popupDatas.push({ uuid, comp: null });

        this.setLoadingMask(true);
        Res.manualLoadPrefab(bdlname, clsname)
            .then(prefab => {
                let parent = Res.getRoot(WanderCommon.LayerMap.Popup);
                let node = instantiate(prefab),
                    comp = node.getComponent(clsname) as BasePopup;

                comp.setData(data);
                comp.isHideMask = hideMask;
                comp.suffix = suffix ? suffix : '';
                parent.addChild(node);

                this.insertComp(uuid, comp);
                this.checkTopPopupStat(depth);

                Log.i(`create popup ${uuid} ok!`);
                this.setLoadingMask(false);
            })
            .catch((err: Error) => {
                Log.e(`create ${clsname} err: ` + err.message);
                this.removePopupData(uuid);
                this.setLoadingMask(false);
            });
    }

    /**
     * 直接嵌入场景的弹窗
     * @param node
     * @param data
     * @param hideMask
     * @returns
     */
    public directlyPopup(node: Node, data?: any, hideMask: boolean = false) {
        const uuid = node.name;
        for (let popup of this._popupDatas) {
            if (popup.uuid == uuid) return;
        }
        this._popupDatas.push({ uuid, comp: null });

        node.removeFromParent();
        let parent = Res.getRoot(WanderCommon.LayerMap.Popup);
        parent.addChild(node);

        let comp = node.getComponent(node.name) as BasePopup
        comp.setData(data);
        comp.isHideMask = hideMask;

        this.insertComp(uuid, comp);
        this.checkTopPopupStat(0);
    }

    public findPanel<T extends BasePanel>(clsName: string): T | null {
        const uuid = clsName;
        for (let panelData of this._panelDatas) {
            if (panelData.uuid == uuid && panelData.comp && isValid(panelData.comp)) return panelData.comp as T;
        }
        return null;
    }

    public findPopup<T extends BasePopup>(clsName: string, suffix?: string): T | null {
        const uuid = suffix ? `${clsName}_${suffix}` : clsName;
        for (let popupData of this._popupDatas) {
            if (popupData.uuid == uuid && popupData.comp && isValid(popupData.comp)) return popupData.comp as T;
        }
        return null;
    }

    /**
     * 关闭 panel
     * @param clsname
     * @param mode 0:立即销毁；!=0:手动销毁
     */
    public closePanel(clsname: string, mode = 0) {
        this.removePanelData(clsname, mode);

        // 显示当前剩余 panel 中的最上面一个
        let panel = this._panelDatas[this._panelDatas.length - 1];
        if (panel && panel.comp && isValid(panel.comp)) {
            panel.comp.setVisible(true);
        }
    }

    public closePopup(uuid: string) {
        this.removePopupData(uuid);

        if (this._popupDatas.length == 0) {
            Res.setPopupMask(false, 0);
        } else {
            this.checkTopPopupStat();
        }
    }

    /**
     * 清除 panel 的引用
     * 当切换场景时，随着 Canvas 销毁，子节点也会销毁
     */
    public clearRefs() {
        this._panelDatas = [];
        this._popupDatas = [];
    }

    ///////////////////////////////
    private removePanelData(clsname: string, mode: number) {
        for (let l = this._panelDatas.length, i = l - 1; i >= 0; --i) {
            let panel = this._panelDatas[i];
            if (panel.uuid == clsname) {
                this._panelDatas.splice(i, 1);
                if (panel.comp && isValid(panel.comp)) {
                    if (mode == 0) {
                        panel.comp.node.destroy();
                    } else {
                        panel.comp.onManualDestroy(mode);
                    }
                }
                break;
            }
        }
    }

    private removePopupData(uuid: string) {
        for (let l = this._popupDatas.length, i = l - 1; i >= 0; --i) {
            let popup = this._popupDatas[i];
            if (popup.uuid == uuid) {
                this._popupDatas.splice(i, 1);
                if (popup.comp && isValid(popup.comp)) {
                    popup.comp.node.destroy();
                }
                break;
            }
        }
    }

    private insertComp<T extends Foundation>(uuid: string, comp: T) {
        if (comp instanceof BasePanel) {
            for (let panel of this._panelDatas) {
                if (panel.uuid == uuid) {
                    panel.comp = comp;
                    break;
                }
            }
        } else if (comp instanceof BasePopup) {
            for (let panel of this._popupDatas) {
                if (panel.uuid == uuid) {
                    panel.comp = comp;
                    break;
                }
            }
        }
    }

    private checkTopPopupStat(depth: number = 0) {
        for (let l = this._popupDatas.length, i = l - 1; i >= 0; --i) {
            let popup = this._popupDatas[i];
            if (popup.comp && isValid(popup.comp)) {
                // depth distance from top
                popup.comp.setVisible(i >= l - 1 - depth);

                // top pupup make mask stat
                if (i == l - 1) {
                    Res.setPopupMask(!popup.comp.isHideMask, depth);
                }
            }
        }
    }


    private _checkFlyMsg() {
        let msgData = this._flyMsgArr.shift();
        if (msgData) {
            this._isFlying = true;
            let node = Res.getToastInst(Res.getRoot(WanderCommon.LayerMap.Toast));
            let comp = node.getComponent(Toast)!;
            comp.setText(msgData.msg, msgData.colorHex);
            node.setScale(0.2, 0.2);
            node.setPosition(0, 0);
            node.getComponent(UIOpacity)!.opacity = 255;
            let t = tween;
            t(node)
                .parallel(
                    t().by(0.3, { position: v3(0, 50) }),
                    t().to(0.3, { scale: v3(1, 1) })
                )
                .by(0.5, { position: v3(0, 50) })
                .call(() => {
                    this._checkFlyMsg();
                })
                .delay(0.2)
                .parallel(
                    t().call(() => {
                        tween(node.getComponent(UIOpacity)).to(0.5, { opacity: 0 }).start();
                    }),
                    t().by(0.5, { position: v3(0, 60) })
                )
                .call(() => {
                    Pool.putNode(node);
                })
                .start()
        }
        else {
            this._isFlying = false;
        }
    }

    private setLoadingMask(visible: boolean) {
        if (visible) {
            if (!isValid(this._loadingMask)) {
                this._loadingMask = Res.getLoadingMaskInst(Res.getRoot(WanderCommon.LayerMap.LoadRes));
            }
            this._loadingMask!.active = true;
        } else if (isValid(this._loadingMask)) {
            this._loadingMask!.active = false;
        }
    }

    public setWebsocketConnecting(visible: boolean) {
        if (visible) {
            if (!isValid(this._websocketConnecting)) {
                this._websocketConnecting = Res.getWebsocketConnectingInst(Res.getRoot(WanderCommon.LayerMap.WebSocketConnecting));
            }
            this._websocketConnecting!.active = true;
        } else if (isValid(this._websocketConnecting)) {
            this._websocketConnecting!.active = false;
        }
    }


    //private
    private _panelDatas: IPanelData[] = [];
    private _popupDatas: IPopupData[] = [];
    private _flyMsgArr: { msg: string, colorHex: string }[] = [];
    private _isFlying: boolean = false;
    private _loadingMask: Node | null = null;
    private _websocketConnecting: Node | null = null;

}

const Gui = GuiMgr.getInstance() as GuiMgr
export default Gui;